﻿using System;
using System.Collections;
using System.Globalization;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reactive.Linq;
using System.Threading.Tasks;
using System.Windows.Markup;
using System.Runtime.InteropServices;

using agree.configuration;
using agree.schema;

using alib.Collections;
using alib.Collections.ReadOnly;
using alib.Concurrency;
using alib.Debugging;
using alib.Enumerable;
using alib.BitArray;

namespace agree
{
	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	/// <summary>
	/// Enumerate the set of EPs that initially populate the generation chart
	/// find semantically contentful lexical entries and skolemize them in all possible ways
	/// </summary>
	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	public class LookupPredLexicalEntries : m_observable<GenerationInput, IChartObj<CoverageMap>>,
		ρρPredicateIndex
	{
		public LookupPredLexicalEntries(IRtParent parent, GenerationInput input)
			: base(parent, input)
		{
		}

		///////////////////////////////////////////////////////////////////////
		///
		PredicateIndex idx;
		public PredicateIndex PredicateIndex
		{
			get { return idx; }
			set { idx = value; }
		}
		///
		///////////////////////////////////////////////////////////////////////

		protected override void start(GenerationInput input)
		{
			HashSet<LexLookupKey> item_keys = input.RelKeys.ToHashSet();

			BitArr check_les = new BitArr(idx.Lexicon.LexicalEntries.Count);

			int num_lexent = 0;
			foreach (LexicalEntry le in input.RelKeys.SelectMany(k => idx.LookupLexEntryRel(k)))
			{
				if (check_les[le.SubIndex])
					continue;
				check_les[le.SubIndex] = true;

				/// to be included, all of the LE's relations must be present in the input
				if (le.RelKeys.All(r => item_keys.Contains(r)))
				{
					foreach (var gxo in le.SkolemizeFor(input))
					{
						_item(gxo);
						num_lexent++;
					}
				}
			}

			Debug.Print("found {0} lex entries by pred lookup", num_lexent);
			_remove_task();
		}
	};


	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	/// <summary>
	/// 
	/// </summary>
	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	[UsableDuringInitialization(true)]
	public class DuplicateLexIds : ItemsList<String>, IIdentity,
		ρρEntryMgr
	{
		public DuplicateLexIds(IRtParent parent)
			: base(parent)
		{
			this.f_applied = false;
		}

		///////////////////////////////////////////////////////////////////////
		/// 
		[DebuggerBrowsable(DebuggerBrowsableState.Never)]
		EntryMgr em;
		public EntryMgr EntryMgr
		{
			get { return em; }
			set { em = value; }
		}
		///
		///////////////////////////////////////////////////////////////////////

		bool f_applied;
		public bool Contains(LexicalEntry ent)
		{
			if (!f_applied)
			{
				do_pre_cache();
				f_applied = true;
			}
			return ent != null && ent.f_gen_ignore;
		}

		void do_pre_cache()
		{
			if (em == null)
				throw new Exception();
			LexicalEntry le;
			lock (this)
				foreach (var s in this)
					if (em.entry_dict.TryGetValue(s, out le))
						le.f_gen_ignore = true;
		}
	};

	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	/// <summary>
	/// tuple of (PRED,CARG). Used in generation for lex lookup
	/// </summary>
	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	[StructLayout(LayoutKind.Explicit, Pack = 4, Size = 8)]
	public struct LexLookupKey : IEquatable<LexLookupKey>
	{
		public static readonly LexLookupKey NotValid = new LexLookupKey(Edge.Flag.Bottom, Edge.Flag.Bottom);

		public LexLookupKey(String s)
			: this()
		{
			this._ul = ulong.Parse(s, NumberStyles.HexNumber);
		}
		public LexLookupKey(Edge.Flag pred, Edge.Flag carg = 0)
			: this()
		{
			this.pred = pred;
			this.carg = carg;
		}
		public LexLookupKey(Tfs tfs, int m)
			: this()
		{
			int _unwanted;
			this.pred = tfs.TryGetFlagsMark(tfs.mrsm.ifeat_pred, m, out _unwanted) & Edge.Flag.IdResolve;
			this.carg = tfs.TryGetFlagsMark(tfs.mrsm.ifeat_carg, m, out _unwanted) & Edge.Flag.IdResolve;
			Debug.Assert(this.IsValid);
		}
		public LexLookupKey(TfsSlot ts)
			: this(ts.tfs, ts.out_mark)
		{
		}
		[FieldOffset(0)]
		readonly ulong _ul;
		[FieldOffset(0)]
		public readonly Edge.Flag pred;
		[FieldOffset(4)]
		public readonly Edge.Flag carg;

		public bool IsValid { get { return pred > 0; } }

		public bool Equals(LexLookupKey other) { return this._ul == other._ul; }

		public override bool Equals(Object obj) { return obj is LexLookupKey && this._ul == ((LexLookupKey)obj)._ul; }

		public override int GetHashCode() { return (int)(pred ^ carg); }

		public override String ToString() { return _ul.ToString("X16"); }

		public String PersistentKey(TypeUtils tu)
		{
			return String.Format("{0}\t{1}",
				tu.TypeNameOrStringValueDisplay(pred),
				carg == 0 ? "" : tu.TypeNameOrStringValueDisplay(carg));
		}

		public static bool operator ==(LexLookupKey k1, LexLookupKey k2) { return k1._ul == k2._ul; }
		public static bool operator !=(LexLookupKey k1, LexLookupKey k2) { return k1._ul != k2._ul; }
	};


	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	/// <summary>
	/// index semantic predicates for generation. Thanks to monotonicity (semantic composition principle)
	/// an LE is only accepted for generation if *all* of its EPs are present in the generation input,
	/// and this in turn means that we only have to index by one of the LE's predicates.
	/// </summary>
	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	public class PredicateIndexLoader : _monad<Lexicon, DuplicateLexIds>,
		ρρLexicon,
		ρρDuplicateLexIds
	{
		public PredicateIndexLoader(IRtParent parent, Lexicon lex, DuplicateLexIds dup_lex_ids)
			: base(parent, lex, dup_lex_ids)
		{
		}
		public PredicateIndexLoader(IRtParent parent, Lexicon lex)
			: base(parent, lex)
		{
		}
		public PredicateIndexLoader(IRtParent parent)
			: base(parent)
		{
		}

		///////////////////////////////////////////////////////////////////////
		/// 
		[DebuggerBrowsable(DebuggerBrowsableState.Never)]
		public Lexicon Lexicon
		{
			get { return base.t0; }
			set { base.t0 = value; }
		}
		///
		///////////////////////////////////////////////////////////////////////


		///////////////////////////////////////////////////////////////////////
		/// 
		public DuplicateLexIds DuplicateLexIds
		{
			get { return base.t1; }
			set { base.t1 = value; }
		}
		///
		///////////////////////////////////////////////////////////////////////

		public ReadOnlyDictionary<LexLookupKey, LexicalEntry[]> result;

		public override Object ProvideValue(IServiceProvider sp)
		{
			if (base.t0 == null)
				throw new Exception();

			return Task.Factory.StartNew<PredicateIndexLoader>(() =>
			{
				var opts = t0.AgreeConfig.Lexicon.RelIndexCache;

				if (opts.HasFlag(LexiconConfig.GenIndexCacheMode.Read) && (result = _read()) != null)
					return this;

				result = build();

				if (opts.HasFlag(LexiconConfig.GenIndexCacheMode.Write))
					Task.Factory.StartNew(() => _write(result));

				return this;
			});
		}

		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		///
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		ReadOnlyDictionary<LexLookupKey, LexicalEntry[]> build()
		{
			Debug.Write("indexing lexicon for generation...");
#if DONT_LOOKUP_TRIGGER_RULE_TARGETS
			hsle = t0.em.TriggerRuleMgr.ToHashSet(g => g.le);
			Debug.Print("filtering {0} trigger-rule-targeted lexical entries", hsle.Count);
#endif
			this.cd = new Dictionary<LexLookupKey, RefList<LexicalEntry>>();

			TaskControl.ParallelLoop(t0.LexicalEntries, _create_lex_index_entry);

			var ro = cd.ToReadOnlyDictionary(kvp => kvp.Key, kvp =>
			{
				var lle = kvp.Value.GetTrimmed();
				Array.Sort(lle, _rel_le_order.Instance);
				return lle;
			});

			this.cd = null;

			Debug.WriteLine("done");
			return ro;
		}

#if DONT_LOOKUP_TRIGGER_RULE_TARGETS
		HashSet<LexicalEntry> hsle;
#endif
		Dictionary<LexLookupKey, RefList<LexicalEntry>> cd;

		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		///
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		void _create_lex_index_entry(LexicalEntry ent)
		{
			LexLookupKey key;
			RefList<LexicalEntry> lle;

			if (t1 != null && t1.Contains(ent))	/// duplicate-lex-ids
				return;

			if (!(key = ent.IndexedRelation).IsValid)
				return;

#if DONT_LOOKUP_TRIGGER_RULE_TARGETS
			if (hsle.Contains(ent))
				return;
#endif

			lock (cd)
			{
				if (cd.TryGetValue(key, out lle))
					lle.Add(ent);
				else
					cd.Add(key, new RefList<LexicalEntry> { ent });
			}
		}


		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		///
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		String CacheFileName
		{
			get { return String.Format("{0}-{1:X8}.pred-idx", t0.AgreeConfig.Name, t0.TdlHash); }
		}

		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		///
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		void _write(ReadOnlyDictionary<LexLookupKey, LexicalEntry[]> dict)
		{
			using (StreamWriter sw = new StreamWriter(CacheFileName))
			{
				var tu = t0.tu;
				foreach (var q in dict.OrderBy(kvp => kvp.Value[0].Name))
					//sw.WriteLine("{0}\t{1}", q.Key.ToString(), q.Value.Select(le => le.Name).StringJoin("\t"));
					sw.WriteLine("{0}\t{1}", q.Key.PersistentKey(tu), q.Value.Select(le => le.Name).StringJoin("\t"));
			}
			Debug.Print("Wrote " + Path.GetFullPath(CacheFileName));
		}

		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		///
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		ReadOnlyDictionary<LexLookupKey, LexicalEntry[]> _read()
		{
			if (!File.Exists(CacheFileName))
			{
				Debug.Print("did not find pred cache file {0}", CacheFileName);
				return null;
			}

			Debug.Print("load pred cache file {0}", CacheFileName);
			return File.ReadAllLines(CacheFileName)
						.Select(l => l.Split(alib.Character.Charset.tab))
						.ToReadOnlyDictionary(
								rgs =>
								{
									Edge.Flag pred, carg;
									pred = t0.tu.StringToTypeOrStringValue(rgs[0]);
									carg = rgs[1].Length == 0 ? 0 : t0.tu.StringToTypeOrStringValue(rgs[1]);
									return new LexLookupKey(pred, carg);
								},
								rgs => rgs.Skip(2).SelectNotNull(s_name => t0.em[s_name] as LexicalEntry).ToArray());
		}

		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>
		/// Order of LEs in the rel lookup determines the order that LEs are added to the generation chart. To maximize
		/// traction for index accessibility, the primary criterion for this order is the (descending) number of MRS
		/// variables introduced by the lexical entry. A secondary sort is provided for determinism's sake, because 
		/// the order affects how much packing opportunity will be found, even in single-threaded mode.
		/// </summary>
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		class _rel_le_order : IComparer<LexicalEntry>
		{
			public static IComparer<LexicalEntry> Instance = new _rel_le_order();

			public unsafe int Compare(LexicalEntry x, LexicalEntry y)
			{
#if false
				int q = num_vars(y) - num_vars(x);
				if (q != 0)
					return q;
#endif
				return x._id - y._id;
			}

			int num_vars(LexicalEntry le)
			{
#if true
				var atfs = le.Expanded;
				var pmrs = new Pmrs.PortableMrs(le.Parent, atfs);
				var sktfs = pmrs.Write(le.Grammar.fsu, le.Grammar.ftm.r_none, null, Tfs.Flags.None);
				ulong vars = sktfs.FindAccessibleIndices();
#else
				var atfs = (ArrayTfs)le.Expanded;
				ulong vars = atfs.FindAccessibleIndices();
#endif
				int c = bit_count(vars);
				return c;
			}

			int bit_count(ulong ul)
			{
				int c = 0;
				while (ul != 0)
				{
					ul ^= ul & (ulong)-(long)ul;
					c++;
				}
				return c;
			}
		};
	};


	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	/// <summary>
	/// generation index
	/// </summary>
	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	public class PredicateIndex : identity_named,
		ρρLexicon
	{
		public PredicateIndex(IIdentity prv)
			: base(prv)
		{
		}

		///////////////////////////////////////////////////////////////////////
		/// 
		Lexicon lex;
		[DebuggerBrowsable(DebuggerBrowsableState.Never)]
		public Lexicon Lexicon
		{
			get { return lex; }
			set { lex = value; }
		}
		/// 
		///////////////////////////////////////////////////////////////////////

		Task<ReadOnlyDictionary<LexLookupKey, LexicalEntry[]>> task;
		public Task<PredicateIndexLoader> Loader
		{
			set
			{
				this.task = value.ContinueWith(t =>
				{
					var _ldr = t.Result;
					if (lex == null)
						lex = _ldr.Lexicon;
					else if (lex != _ldr.Lexicon)
						throw new Exception("Lexicon does not match predicate index");
					return _ldr.result;
				});
				this.task.Wait();
			}
		}

		public LexicalEntry[] LookupLexEntryRel(LexLookupKey rel)
		{
			LexicalEntry[] rgle;
			var le_rel_lookup = task.Result;
			return le_rel_lookup.TryGetValue(rel, out rgle) ? rgle : Collection<LexicalEntry>.None;
		}
	};
}
